﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Shapes;
using System.Drawing;
using Silvermoon.Utils;

using Silvermoon.Windows;
using Silvermoon.Images;
using Silvermoon.UI;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Control to show and change a range.
    /// </summary>
    /// <remarks>
    /// Slider is implemented using 3 different shapes:
    /// a) the filled shape which appears at the left.
    /// b) the unfilled (or background) shape which appears at the right.
    /// c) the button between filled and unfilled shape.
    /// filled and unfilled shapes are rounded at both the left and the right, but since the button overlapps that area, this is not visbile and both shapes appear as one.
    /// </remarks>
    public class Slider : ChromeControl
    {
        #region const

        /// <summary>
        /// Specifies the height of the range.
        /// </summary>
        public const int RangeSize = 24;

        /// <summary>
        /// Specifies width and height of the button.
        /// </summary>
        public const int DefaultButtonSize = 48;

        #endregion
        #region accessors

        public static PropertyAccessor<Slider, Color> ButtonColorProperty = PropertyAccessor.Register<Slider, Color>(
            "ButtonColor",
            (e) => e.ButtonColor,
            (e, v) => e.ButtonColor = v);


        public static PropertyAccessor<Slider, int> ButtonOpacityProperty = PropertyAccessor.Register<Slider, int>(
            "ButtonOpacity",
            (e) => e.ButtonOpacity,
            (e, v) => e.ButtonOpacity = v);



        public static PropertyAccessor<Slider, Color> FillColorProperty = PropertyAccessor.Register<Slider, Color>(
            "FillColor",
            (e) => e.FillColor,
            (e, v) => e.FillColor = v);

        public static PropertyAccessor<Slider, int> ValueProperty = PropertyAccessor.Register<Slider, int>(
            "Value",
            (e) => e.Value,
            (e, v) => e.Value = v);

        public static readonly PropertyAccessor<Slider, ScaleTransform> ButtonScaleProperty = PropertyAccessor.Register<Slider, ScaleTransform>(
            "ButtonScale",
            (e) => e.buttonScale,
            (e, v) => e.ButtonScale = v
            );


        #endregion
        #region fields

        private Color fillColor;
        private bool isReadonly = false;
        private ScaleTransform buttonScale;
        private Transformation[] buttonTransformations;
        private bool isButtonPressed;
        private Shape button;
        private Shape fillShape;
        private int minValue;
        private int maxValue = 100;
        private int value;
        private int downValue;

        #endregion
        #region ctors

        public static readonly VisualStateManager SliderManager = new VisualStateManager(
            new VisualStateGroup(
                new VisualState(VState.Enabled, 1),
                new VisualState(VState.Disabled,
                    new ColorTransition
                    {
                        Property = Slider.ColorProperty,
                        Duration = 350,
                        To = Color.LightGray
                    },
                    new ColorTransition
                    {
                        Property = Slider.ButtonColorProperty,
                        Duration = 350,
                        To = Color.DarkGray
                    },
                    new ColorTransition
                    {
                        Property = Slider.FillColorProperty,
                        Duration = 350,
                        To = Color.Gray
                    }
                    )
            ),
            new VisualStateGroup(
                new VisualState(VState.ButtonReleased, 300),
                new VisualState(VState.ButtonPressed,
                    new ColorTransition
                    {
                        Property = Slider.ButtonColorProperty,
                        To = PhoneColors.Accent,
                        Duration = 10,
                    }
                    )
                )
            );


        static Slider()
        {

            VisualStateManager.RegisterClassManager(typeof(Slider), SliderManager);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TrackBar"/> class.
        /// </summary>
        public Slider()
            : base()
        {
            Height = DefaultButtonSize;
            ControlFlags |= ControlFlags.CanPan;
            buttonScale = new ScaleTransform();
            buttonTransformations = new Transformation[] { buttonScale };
        }

        #endregion
        #region props

        public int ButtonOpacity
        {
            get { return button != null ? button.Opacity : 0; }
            set
            {
                if (button != null && button.Opacity != value)
                {
                    button.Opacity = value;
                    OnButtonOpacityChanged();
                }
            }
        }

        public bool Readonly
        {
            get { return isReadonly; }
            set
            {
                if (isReadonly != value)
                {
                    isReadonly = value;
                    OnReadonlyChanged();
                }
            }
        }

        protected bool IsButtonVisible
        {
            get { return !isReadonly && Enabled && !Shape.IsNullOrEmpty(button); }
        }

        public ScaleTransform ButtonScale
        {
            get { return buttonScale; }
            set
            {
                if (buttonScale != value)
                {
                    buttonTransformations[0] = value;
                    buttonScale = value;
                    button.Invalidate();
                    Invalidate(InvalidationFlag.Transformation);

                }
            }
        }

        protected Size ButtonSize
        {
            get { return !IsButtonVisible ? new Size(0, Height) : button.Size; }
        }

        /// <summary>
        /// Gets or sets the shape for the button.
        /// </summary>
        public Shape Button
        {
            get { return button; }
            set
            {
                if (button != value)
                {
                    if (button != null) Shapes.Remove(button);
                    button = value;
                    if (value != null) Shapes.AddLast(value);
                    if (button != null) button.Transformations = buttonTransformations;
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        /// <summary>
        /// Gets or sets the shape for the filling part.
        /// </summary>
        public Shape Fill
        {
            get { return fillShape; }
            set
            {
                if (fillShape != value)
                {
                    if (fillShape != null) Shapes.Remove(fillShape);
                    fillShape = value;
                    if (value != null) Shapes.Append(Background, value);
                    Invalidate(InvalidationFlag.Appearance);
                }
            }
        }

        /// <summary>
        /// Gets the bounds of the button.
        /// </summary>
        protected Rectangle ButtonRect
        {
            get
            {
                Size size = ButtonSize;
                int x = CalculateOffset();
                if (!IsButtonVisible) return new Rectangle(x, 0, 16, Height);
                int w = size.Width;
                int y = (Height - size.Height) / 2;
                return new Rectangle(x, y, w, size.Height);
            }
        }

        protected override Rectangle BackgroundRect
        {
            get
            {
                Shape b = Background;
                if (b != null)
                {
                    int h = Control.UnscaleY(b.Height);
                    int y = (Height - h) / 2;
                    int x = 2;
                    int w = Width - x - 2;
                    return Control.ScaledRect(x, y, w, h);
                }
                else return Rectangle.Empty;
            }
        }

        protected virtual Rectangle FilledRect
        {
            get
            {
                Shape b = Fill;
                if (b != null)
                {
                    Size size = ButtonSize;
                    int h = Control.UnscaleY(Background.Height);
                    int y = (Height - h) / 2;
                    int x = 2;
                    int w = CalculateOffset() + (button != null ? size.Width / 2 : 0) - 2;
                    if (w < h / 2) w = 0;
                    return Control.ScaledRect(x, y, w, h);
                }
                else return Rectangle.Empty;
            }
        }

        /// <summary>
        /// Gets or sets the minimum value.
        /// </summary>
        public int Minimum
        {
            get { return minValue; }
            set
            {
                if (minValue != value)
                {
                    minValue = value;
                    Value = ValidateValue(this.value);
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        /// <summary>
        /// Gets or sets the maximum value.
        /// </summary>
        public int Maximum
        {
            get { return maxValue; }
            set
            {
                if (maxValue != value)
                {
                    maxValue = value;
                    Value = ValidateValue(this.value);
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        /// <summary>
        /// Gets or sets the value. The range must be within MinValue and MaxValue. Otherwise it is clamped to that range.
        /// </summary>
        public int Value
        {
            get { return value; }
            set
            {
                value = ValidateValue(value);
                if (this.value != value)
                {
                    this.value = value;
                    Invalidate(InvalidationFlag.Layout);
                    OnValueChanged();
                }
            }
        }

        /// <summary>
        /// Gets or sets the color for the button.
        /// </summary>
        public Color ButtonColor
        {
            get { return Button != null ? button.Color : Color.Empty; }
            set
            {
                if (Button != null && button.Color != value)
                {
                    button.Color = value;
                    Invalidate(InvalidationFlag.Color);
                }
            }
        }

        /// <summary>
        /// Gets whether the button is pressed.
        /// </summary>
        public bool IsButtonPressed
        {
            get { return isButtonPressed; }
            set
            {
                if (isButtonPressed != value)
                {
                    isButtonPressed = value;
                    Invalidate(InvalidationFlag.Appearance);
                    OnButtonPressedChanged();
                }
            }
        }

        /// <summary>
        /// Gets or sets the color for the filled area.
        /// </summary>
        public Color FillColor
        {
            get { return fillColor; }
            set
            {
                if (fillColor != value)
                {
                    fillColor = value;
                    Invalidate(InvalidationFlag.Color | InvalidationFlag.Appearance);
                }
            }
        }

        #endregion
        #region methods

        protected virtual void OnButtonOpacityChanged()
        {
            Invalidate(InvalidationFlag.Appearance);
        }

        protected virtual void OnReadonlyChanged()
        {
            SetState(isReadonly ? "Changable" : "Readonly");
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        protected override void OnEnabledChanged()
        {
            base.OnEnabledChanged();
            Invalidate(InvalidationFlag.Appearance);
        }

        protected override void ApplyStyle()
        {
            base.ApplyStyle();
            if (button == null) Button = CreateDefaultButton();
            if (fillShape == null) Fill = CreateDefaultFill();
            if (Color.IsEmpty) Color = Color.White;
            if (FillColor.IsEmpty) FillColor = PhoneColors.Accent;

        }

        protected virtual void OnButtonPressedChanged()
        {
            SetState(isButtonPressed ? VState.ButtonPressed : VState.ButtonReleased);
        }

        protected override void OnLayout()
        {
            base.OnLayout();
            if (button != null) button.Visible = IsButtonVisible;
            if ((fillShape != null) && CheckFlag(InvalidationFlag.Appearance | InvalidationFlag.Color))
            {
                fillShape.Color = fillColor;
            }
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout | InvalidationFlag.Enabled))
            {
                if (IsButtonVisible) Button.Bounds = ButtonRect;
                Fill.Bounds = FilledRect;
            }
        }

        protected internal override void OnTouchDown(TouchEventArgs e)
        {
            if (!e.Handled)
            {
                e.Handled = true;
                //downValue = 0;
                Point p = PointToClient(e.Position);
                //if (!isReadonly) Value = CalculateOffsetFromDeltaPoint(p.X, p.Y);
                downValue = Value;
                Rectangle rect = ButtonRect;
                if (rect.Width == 0)
                {
                    rect.X -= 20;
                    rect.Width += 20;
                }
                IsButtonPressed = (rect.Contains(p));
            }
            base.OnTouchDown(e);
        }

        protected internal override void OnTouchUp(TouchEventArgs e)
        {
            IsButtonPressed = false;
            e.Handled = true;
            base.OnTouchUp(e);
        }


        private int CalculateOffsetFromDeltaPoint(int dx, int dy)
        {
            int div = Width;
            if (div == 0) div = int.MaxValue;
            return (maxValue - minValue) * dx / div + downValue;
        }

        private int CalculateOffset()
        {
            int d = (maxValue - minValue);
            if (d == 0) d = int.MaxValue;
            int bw = ButtonSize.Width;
            int w = (Width - bw) * (value - minValue) / d;
            return w;
        }

        /// <summary>
        /// Occurs when the value has changed.
        /// </summary>
        protected virtual void OnValueChanged()
        {
            if (ValueChanged != null) ValueChanged(this, EventArgs.Empty);
        }

        private int ValidateValue(int value)
        {
            if (value < minValue) return minValue;
            if (value > maxValue) return maxValue;
            return value;
        }

        /// <summary>
        /// Creates the default shape for the filling part.
        /// </summary>
        protected virtual Shape CreateDefaultFill()
        {
            return new TrackbarFillShape { Size = new Size(1, RangeSize), Color = PhoneColors.Accent };
        }

        /// <summary>
        /// Creates the default button shape.
        /// </summary>
        protected virtual Shape CreateDefaultButton()
        {
            return new TrackbarButtonShape { Size = new Size(DefaultButtonSize, DefaultButtonSize) };
        }

        /// <summary>
        /// Creates the default background shape. Depending on Orientation, either Width or Height must be specified, while the other value is maintained on layout.
        /// </summary>
        protected override Shape CreateDefaultBackground()
        {
            return new TrackbarShape { Size = new Size(1, RangeSize), Color = Color.White };
        }

        protected internal override void OnBeginPan(PanEventArgs e)
        {
            if (e.HorizontalMotion && !isReadonly)
            {
                e.Handled = IsButtonPressed;
                e.Handled = true;
                e.Value.X = 0;
            }
            base.OnBeginPan(e);
        }

        protected internal override void OnPan(PanEventArgs e)
        {
            if (!isReadonly)
            {
                e.Handled = true;
                Value = CalculateOffsetFromDeltaPoint(e.Value.X, e.Value.Y);
            }
            base.OnPan(e);
        }

        #endregion
        #region events

        /// <summary>
        /// Occurs when the value has changed.
        /// </summary>
        public event EventHandler ValueChanged;

        #endregion
    }
}
